package io.github.wanggit.antrpc.commons.bean;

import io.github.wanggit.antrpc.commons.annotations.RpcMethod;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class RegisterBeanHelper {

    private RegisterBeanHelper() {
        throw new IllegalStateException();
    }

    private static final String[] JAVA_BASIC_METHODS =
            new String[] {
                "finalize()",
                "toString()",
                "notifyAll()",
                "equals(java.lang.Object)",
                "hashCode()",
                "wait(long,int)",
                "notify()",
                "clone()",
                "wait(long)",
                "wait()",
                "getClass()",
                "registerNatives()"
            };

    static {
        Arrays.sort(JAVA_BASIC_METHODS);
    }

    private static final ConcurrentHashMap<String, RegisterBeanMethod> cache =
            new ConcurrentHashMap<>();

    public static RegisterBeanMethod[] findMethodsWithoutJavaBasicMethods(Class<?> clazz) {
        Method[] declaredMethods = ReflectionUtils.getAllDeclaredMethods(clazz);
        List<RegisterBeanMethod> methods = new ArrayList<>();
        for (Method declaredMethod : declaredMethods) {
            RegisterBeanMethod registerBeanMethod = getRegisterBeanMethod(declaredMethod);
            int idx = Arrays.binarySearch(JAVA_BASIC_METHODS, registerBeanMethod.toString());
            if (idx < 0) {
                methods.add(registerBeanMethod);
            }
        }
        return methods.toArray(new RegisterBeanMethod[] {});
    }

    public static RegisterBeanMethod getRegisterBeanMethod(Method method) {
        return internalGetRegisterBeanMethod(method);
    }

    public static RegisterBeanMethod[] findMethodsWithoutJavaBasicMethods(String className) {
        try {
            return findMethodsWithoutJavaBasicMethods(
                    ClassUtils.forName(className, RegisterBeanHelper.class.getClassLoader()));
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("Not Found Class " + className, e);
            }
        }
        return null;
    }

    private static RegisterBeanMethod internalGetRegisterBeanMethod(Method method) {
        String methodStr = method.toGenericString();
        if (!cache.containsKey(methodStr)) {
            synchronized (methodStr.intern()) {
                if (!cache.containsKey(methodStr)) {
                    RegisterBeanMethod registerBeanMethod = methodToRegisterBeanMethod(method);
                    cache.put(methodStr, registerBeanMethod);
                }
            }
        }
        return cache.get(methodStr);
    }

    private static RegisterBeanMethod methodToRegisterBeanMethod(Method method) {
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        List<String> parameterTypeNames = new ArrayList<>(parameterTypes.length * 2);
        for (Class<?> parameterType : parameterTypes) {
            String parameterTypeName = parameterType.getName();
            parameterTypeNames.add(parameterTypeName);
        }
        RegisterBeanMethod registerBeanMethod = new RegisterBeanMethod();
        registerBeanMethod.setMethodName(methodName);
        registerBeanMethod.setParameterTypeNames(parameterTypeNames);
        registerBeanMethod.setClassName(method.getDeclaringClass().getName());
        RpcMethod rpcMethod = AnnotationUtils.findAnnotation(method, RpcMethod.class);
        if (null != rpcMethod && rpcMethod.rateLimitEnable()) {
            registerBeanMethod.setLimit(rpcMethod.limit());
            registerBeanMethod.setDurationInSeconds(rpcMethod.durationInSeconds());
        }
        // 生成可读性高的方法全名，主要把数组类型的参数做替换，比如: [I -> int[] 的转换
        registerBeanMethod.generateFullName();
        return registerBeanMethod;
    }
}
